package primitive3D;

import static rayCaster.ApplicationInterface.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;
import javax.vecmath.Point3d;
import rayCaster.Ray;


public class Cube extends Primitive3D {	
	private double sideLen;	
	private Point3d[] topPoints;
	private Point3d[] bottomPoints;
	
	/*
	 * By default, a cube is created with its base on the X-Y plane centered to
	 * the coordinates orgin, its side length is set to 1, and its position 
	 * is at (0, 0, 0.5)
	 * All this default values are defined in LibInterface
	 */
	public Cube(){
		sideLen = CUBE_SIDE_LEN;
		position = new Point3d( CUBE_POSITION );
		name = new String("");
		color = CUBE_COLOR;
		components = new ArrayList<Primitive3D>();
		/*
		 * To build the 6 Quadrilaterals to form this cube, we need
		 * to find out 8 points in space, and we know that all this
		 * points meet:
		 * x = 1/2  or  x = - 1/2
		 * y = 1/2  or  y = - 1/2
		 * z = 0    or  z = 1
		 * So, we will generate the 8 values making combinations and separating
		 * them into 2 sets: Points at the top, and Points at the bottom
		 */
		double[] possibleValues = {sideLen/2, -sideLen/2};
		int len = possibleValues.length;
		
		topPoints = new Point3d[len*len];
		bottomPoints = new Point3d[len*len];
		/*
		int index = 0;
		for( int i=0 ; i < len ; i++ ){
			for( int j=0 ; j < len ; j++ ){
				topPoints[index] = new Point3d(possibleValues[i], possibleValues[j], 1);
				bottomPoints[index++] = new Point3d(possibleValues[i], possibleValues[j], 0);
			}
		}
		*/
		bottomPoints[0]=new Point3d(possibleValues[0],possibleValues[0], 0 );
		bottomPoints[1]=new Point3d(possibleValues[1],possibleValues[0], 0 );
		bottomPoints[2]=new Point3d(possibleValues[1],possibleValues[1], 0 );
		bottomPoints[3]=new Point3d(possibleValues[0],possibleValues[1], 0 );
		
		topPoints[0]=new Point3d(possibleValues[0],possibleValues[0], 1 );
		topPoints[1]=new Point3d(possibleValues[1],possibleValues[0], 1 );
		topPoints[2]=new Point3d(possibleValues[1],possibleValues[1], 1 );
		topPoints[3]=new Point3d(possibleValues[0],possibleValues[1], 1 );
		
		//We create the top Quadrilateral and the bottom Quadrilateral
		ArrayList<Point3d[]>genPoints = new ArrayList<Point3d[]>();
		genPoints.add(bottomPoints);
		genPoints.add(topPoints);
		for( int i=0 ; i < genPoints.size() ; i++ ){		
			Quadrilateral q = new Quadrilateral(genPoints.get(i));
			q.setColor(CUBE_COLOR);
			components.add(q);
		}		
		
		/*
		 * The 6 remaining by pairing points in the bottomPoints array with
		 * the condition that they are adjacent. This is done by inspecting
		 * the x component and y component only in the bottomPoints array.
		 * IDEA:
		 * (x1, y1) and (x2,y2) => If adjacent there should be only one
		 * component changed (1/2 to -1/2, or -1/2 to 1/2)
		 * Thus, if x component is changed => ( x1 * x2 ) < 0
		 * and if y component is changed => (y1 * y2 ) < 0
		 * We can multiply components and count the negatives results. 
		 * If there is only one negative result => They are adjacent
		 * If there are 2 negative results => They are NOT adjacent
		 * In the code we multiply both results and detect the sign
		 * If negative total result => Only one change => Adjacent		
		 * Finally, if a pair is adjacent, we took the corresponding points
		 * in the topPoints array (same indexes) to build a new Quadrilateral
		 * (This is possible because of the construction of both arrays) 
		 */
		len = bottomPoints.length;
		/*
		for( int i=0 ; i < len ; i++ ){
			for( int j=i+1 ; j < len ; j++ ){
				//System.out.println("i="+i+", j="+j); //DEBUG
				//System.out.println("p i = " + bottomPoints[i]+"p j = " + bottomPoints[j]); //DEBUG
				if( areAdjacentPointsInTheCube( bottomPoints[i],bottomPoints[j] ) ){
					Quadrilateral q = new Quadrilateral(bottomPoints[i],bottomPoints[j],topPoints[i],topPoints[j]);
					q.setColor(LibInterface.CUBE_COLOR);
					components.add(q);
				}						
			}
		}	
		*/
		for( int i= 0 ; i<len ; i++){
			Quadrilateral q = new Quadrilateral(bottomPoints[i],bottomPoints[(i+1)%len],topPoints[(i+1)%len],topPoints[i]);
			q.setColor(CUBE_COLOR);
			components.add(q);
		}
		
	}
	public Cube(double sideLen){
		this();
		double scaleFactor = sideLen/CUBE_SIDE_LEN;
		this.scale(scaleFactor);
	}
	public Cube(double sideLen, Point3d position){
		this(sideLen);
		setPosition(position); 
	}	
	public void scale(double scaleFactor){	
		scaleFromReferencePosition(this.position, scaleFactor);
	}
	public double getSideLength(){
		return sideLen;
	}
	
	@Override
	public String toString() {
		StringBuffer str = new StringBuffer();
		StringBuffer vertexStr = new StringBuffer();
		
		for( int i=0 ; i< topPoints.length ; i++ ){
			vertexStr.append(bottomPoints[i].toString() + "\n");
			vertexStr.append(topPoints[i].toString() + "\n");			
		}	
		
		str.append("Cube \"" + getName() + "\"\nVertex = \n" + vertexStr + "\nPosition = " + position + "\nColor = " + color);
		return str.toString();
	}	
	
	@Override
	public void scaleFromReferencePosition(Point3d referencePosition, double scaleFactor) {
		for( int i=0 ; i < components.size() ; i++ ){
			components.get(i).scaleFromReferencePosition(referencePosition, scaleFactor);
		}
		for( int i=0 ; i < topPoints.length ; i++ ){
			scalePointfromReferencePosition(topPoints[i], referencePosition, scaleFactor);
			scalePointfromReferencePosition(bottomPoints[i], referencePosition, scaleFactor);
		}			
		sideLen = sideLen*scaleFactor;
		
	}
	
	@Override
	public void setPosition( Point3d otherPosition ){		
		Point3d componentPosition;
		Point3d delta = new Point3d();		
		
		for(int i=0 ; i < components.size() ; i++ ){
			componentPosition = components.get(i).getPosition();			
			delta.sub(otherPosition, this.position);			
			componentPosition.add(delta);			
			components.get(i).setPosition(componentPosition);
		}
		for( int i=0 ; i < topPoints.length ; i++ ){
			topPoints[i].sub(this.position);
			topPoints[i].add(otherPosition);
			bottomPoints[i].sub(this.position);
			bottomPoints[i].add(otherPosition);
		}		
		position = new Point3d(otherPosition);
	}
	
	@SuppressWarnings("unused")
	private boolean areAdjacentPointsInTheCube(Point3d p1, Point3d p2){
		/*
		 * If changeInX < 0 => There was a change in X
		 * If changeInY < 0 => There was a change in Y
		 */		
		
		double changeInX = p1.x * p2.x;
		double changeInY = p1.y * p2.y;
				
		//It is not possible that there is NO change in X and NO change in Y
		//because topPoints and bottomPoints arrays contains NON EQUALS points
		//return signum( changeInY*changeInX ) == -1.0;
		return !(changeInX < 0 && changeInY < 0); 
	}
	
	// Devuelve la cantidad de puntos intersectados en el nombre de
	// la funcion y los puntos en el arreglo pts de longitud 2.  
	public int intersects(Ray ray, Point3d[] pts) throws Exception {
		Set<Point3d> ptsAux = new HashSet<Point3d>();		
		for( Primitive3D aPrimitive : components ) {			
			if( aPrimitive.intersects(ray, pts) == 1 ) {				
				ptsAux.add( new Point3d(pts[0]) );
			}
		}
		
		Iterator<Point3d> it = ptsAux.iterator();		
		int cantIntersections = 0;
		if( it.hasNext() ) {
			pts[0] = it.next();
			cantIntersections++;
		}
		while( it.hasNext()) {
			Point3d p = it.next();
			if( !p.epsilonEquals(pts[0], 0.001) ) {				
				pts[1] = p;
				cantIntersections++;
				break;
			}
		}
		return cantIntersections;
		/*
		Iterator<Point3d> it = ptsAux.iterator();		
		for( int i=0; it.hasNext(); i++ ) {
			pts[i] = it.next();
		}
		return ptsAux.size();
		*/
	}

}
